#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <stdint.h>
#include <stdbool.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h> //sleep
#include <libgen.h>// basename
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <linux/if_link.h>//XDP_FLAGS_UPDATE_IF_NOEXIST

#include <net/if.h>
int print_if_nameindex(){
    struct if_nameindex *if_ni, *i;
    if_ni = if_nameindex();
    if(if_ni){
        for (i = if_ni; !(i->if_index == 0 && i->if_name == NULL); i++){
            printf("%s,%d\n",i->if_name,i->if_index);
        }
        if_freenameindex(if_ni);
    }
    return 0;
}
/*
https://www.cnblogs.com/charlieroro/p/13403672.html
Native XDP(XDP_FLAGS_DRV_MODE)
Offloaded XDP(XDP_FLAGS_HW_MODE)
Generic XDP(XDP_FLAGS_SKB_MODE)
samples/bpf/xdp_sample_pkts_user.c
*/
static void usage(const char *prog)
{
	fprintf(stderr,
		"%s: %s  -n <ifname>  -o xxx.o\n\n"
		"OPTS:\n"
		"    -F    force loading prog\n"
		"    -S    use skb-mode\n",
		__func__, prog);
}
static int do_attach(int fd,uint32_t xdp_flags,int if_index,const char *if_name){
    struct bpf_prog_info info = {};
    uint32_t info_len = sizeof(info);
    int prog_id=-1,err = 0;
    err = bpf_xdp_attach(if_index, fd, xdp_flags, NULL);
    if (err < 0) {
        printf("ERROR: failed to attach program to %s\n", if_name);
        return prog_id;
    }
    err = bpf_obj_get_info_by_fd(fd, &info, &info_len);
    if (err) {
        printf("can't get prog info - %s\n", strerror(errno));
        return prog_id;
    }
    prog_id = info.id;
    return prog_id;
}

static int do_detach(int prog_id,uint32_t xdp_flags,int if_index, const char *if_name)
{
	int curr_prog_id = 0;
	int err = 0;
	err = bpf_xdp_query_id(if_index, xdp_flags, &curr_prog_id);
	if (err) {
		printf("bpf_xdp_query_id failed\n");
		return err;
	}
	if (prog_id == curr_prog_id) {
		err = bpf_xdp_detach(if_index, xdp_flags, NULL);
		if (err < 0)
			printf("ERROR: failed to detach prog from %s\n", if_name);
	} else if (!curr_prog_id) {
		printf("couldn't find a prog id on a %s\n", if_name);
	} else {
		printf("program on interface changed, not removing\n");
	}
	return err;
}
void map_get_value_array(int fd, int key, int *value){
    if ((bpf_map_lookup_elem(fd, &key, value)) != 0) {
        fprintf(stderr,"ERR: bpf_map_lookup_elem failed key:0x%X\n", key);
        *value=-1;
	}
}
static volatile bool g_running=true;
static void sig_handler(int signo)
{
    g_running=false;
}
//  ./drop-pkt-reader -n wlxe0e1a997ab49 -o xdp-drop.o -S
int main(int argc, char **argv){
    char if_name[32]={0};
    char bpf_name[32]={0};
    const char *optstr = "n:o:FS";
	int prog_fd, map_fd=-1, opt;
    int prog_id=-1;
	struct bpf_program *prog;
	struct bpf_object *obj;
	struct bpf_map *map;
    uint32_t xdp_flags=XDP_FLAGS_UPDATE_IF_NOEXIST;
    const char *target_map="drop_map";
	if (signal(SIGINT, sig_handler) ||
	    signal(SIGHUP, sig_handler) ||
	    signal(SIGTERM, sig_handler)||
        signal(SIGTSTP,sig_handler)){
		perror("signal");
		return 1;
	}
    while ((opt = getopt(argc, argv, optstr)) != -1) {
        switch (opt) {
            case 'n':{
                memcpy(if_name,optarg,strlen(optarg));
                break;
            }
            case 'o':{
                memcpy(bpf_name,optarg,strlen(optarg));
                break;
            }
            case 'F':{
                xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST;
                break;
            }
            case 'S':{
                xdp_flags |= XDP_FLAGS_SKB_MODE;
                break;
            }
            default:{
                usage(basename(argv[0]));
                return 1;            
            }
        }
    }
    uint32_t if_index=if_nametoindex(if_name);
    printf("%s,%d,%d\n",if_name,if_index,optind);
    if(if_index){
        obj = bpf_object__open_file(bpf_name, NULL);
        if (libbpf_get_error(obj)){
            return 1;
        }
        //may try: bpf_object__find_program_by_title
        prog = bpf_object__next_program(obj, NULL);
        bpf_program__set_type(prog, BPF_PROG_TYPE_XDP);
        if(prog){
            const char *sec_name = bpf_program__section_name(prog);
            printf("sec:%s\n",sec_name);
        }
        int err = bpf_object__load(obj);
        if (err){
            bpf_object__close(obj);
            return 1;
        }

        prog_fd = bpf_program__fd(prog);
        map =bpf_object__find_map_by_name(obj,target_map);
        if(map){
            printf("finding %s \n",target_map);
        }else{            
            printf("finding %s failed\n",target_map);
            bpf_object__close(obj);
            return 1;
        }
        map_fd = bpf_map__fd(map);
        prog_id=do_attach(prog_fd,xdp_flags,if_index,if_name);
    }else{
        printf("nic %s does not exist\n",if_name);
    }
    int value=0;
    while(g_running){
        sleep(5);
        map_get_value_array(map_fd,0,&value);
        if(value>0){
            printf("drop pkts: %d\n",value);
        }
    }
    do_detach(prog_id,xdp_flags,if_index,if_name);
    if(obj){
        bpf_object__close(obj);
    }
    return 0;
}
